// programa server cujo objetivo e receber uma solicitacao de arquivo e enviar-lo. 
//@param porto_servidor tam_buffer
//@porto_servidor = porto em que o servidor esta "bind"
//@tam_buffer = tamanho do buffer a ser utilizado
//@tam_janela = tamanho da janela a  ser utilizado

//bibliotecas utilizadas
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 
#include "protocolo.h"
#define MTU 4096 

//funcao de auxilio para informar erros e abortar
void error(char *msg)
{
	printf("\n%s\n", msg);
	exit(1);
}
//funcao de auxilio para informar como usar o programa
void usage()
{
	printf("Usage: \n ./server port tam_buffer tam_janela\n"); 
	printf("\t port: \t\t  Porto do servidor\n"); 
	printf("\t tam_buffer:\t  Tamanho do buffer a ser utilizado\n"); 
	printf("\t tam_janela:\t  Tamanho da janela a ser utilizado\n"); 
}

int main(int argc, char *argv[])
{
	//verifica numero de argumentos
	if (argc != 4) {
		usage();	
		error("Numero de argumentos invalido");	 
	}

	//declaracoes iniciais 
	int sockfd, newsockfd, bytes_lidos, clilen, tamanhoRecebido, i,enviados=0;
	struct sockaddr_in serv_addr, cli_addr;
	struct hostent *server;
	int port = atoi(argv[1]);
	int buffer_size = atoi(argv[2]);
	int tam_janela = atoi(argv[3]);
	int tamanho_total_pacote = buffer_size + TAMANHO_CABECALHO_COM_CALDA;
	char buffer_conteudo[buffer_size +1];
	char buffer[tamanho_total_pacote];
	char* Msg;  
	char* ack;  
	char arquivo[500]="";
	struct timeval time1, time2;
	FILE *file;
	double totalBytes;
	Pacote pacoteSend, ACK;

	//verifica se o buffer pedido é coerente
	if (buffer_size >= ( MTU - TAMANHO_CABECALHO_COM_CALDA)){

		error("Este buffer nao pode ser passado neste canal, ele é maior que o MTU");
	}




	//inicia a contagem do tempo
	gettimeofday(&time1,NULL);

	//inicia o metodo de geracao de erros
	tp_init();

	//cria o novo socket TCP
	sockfd = tp_socket(port);
	if (sockfd < 0){ 
		error("ERROR abrindo o socket");
	}

	//cria a estrutuca sockaddir_in correspondente ao host
	if ( tp_build_addr(&serv_addr, NULL, port) < 0){
		error("ERROR: Criando sockaddir_in Host");
	}


	//recebe nome do arquivo
	//Espera-se um buffer cheio com cabecalho e checksum
	tamanhoRecebido=tp_recvfrom(sockfd,buffer, tamanho_total_pacote,&cli_addr);

	Pacote recebido;
	//Tamanho recebido pode ser menor que o tamanho máximo do buffer
	stringToPacote(&recebido, buffer,tamanhoRecebido);



	//começa a janela deslizante em si.
	//seta timeout Pro ack
	struct timeval tv;
	tv.tv_sec  = 1;  
	tv.tv_usec = 0;
	setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));


	//Abre o arquivo
	if ( (file = fopen(getConteudo(&recebido),"r")) == NULL ){
		error("Nao consegui abrir o arquivo\n");
	}

	Quadro FEnvia[tam_janela];
	int pct_count=0;
	int NEnvio =0;
	int janelaLivre=tam_janela;
	int qtdNaoConfirmado=0;
	int pacoteEsperado=0;

	//Preenche Fila de Envio da Janela Deslizante
	while(pct_count < tam_janela){
		bytes_lidos= fread(buffer_conteudo,1,buffer_size,file); 
		buffer_conteudo[bytes_lidos]='\0';

		if(bytes_lidos > 0){
			//Cria pacote para envio da janela
			FEnvia[pct_count].confirmado = 0;
			empacota(&(FEnvia[pct_count].pacote),'0',pct_count, bytes_lidos, buffer_conteudo);
			Msg = pacoteToString(&(FEnvia[pct_count].pacote));

			pct_count++;
			//MTU
			tp_sendto(sockfd,Msg,strlen(Msg),&cli_addr);
			totalBytes+=strlen(Msg);
			qtdNaoConfirmado++;
			janelaLivre--;
			NEnvio++;
		}else {
			break;
		}

	}


	//Inicia o processo de regime da janela
	//int esperado

	do{
		tamanhoRecebido=tp_recvfrom(sockfd,buffer, tamanho_total_pacote,&cli_addr);
		buffer[tamanhoRecebido]='\0';

		if( tamanhoRecebido > 0){
			stringToPacote(&ACK,buffer,tamanhoRecebido);

			if(check(&ACK) && (getAck(&ACK) == '1') ){

				//recebeu um ack
				//printf("Esperados: %d == %d \n\n", getNPacote(&ACK), pacoteEsperado);
				if((getNPacote(&ACK) )  == pacoteEsperado){
					bytes_lidos= fread(buffer_conteudo,1,buffer_size,file); 
					buffer_conteudo[bytes_lidos]='\0';

					if( bytes_lidos > 0){

						empacota(&(FEnvia[(pacoteEsperado) % tam_janela].pacote),'0',pct_count, bytes_lidos, buffer_conteudo);
						pct_count++; 
						Msg = pacoteToString(&(FEnvia[( pacoteEsperado + 1 ) % tam_janela].pacote));


						FEnvia[pacoteEsperado %tam_janela].confirmado=0;
						tp_sendto(sockfd,Msg,strlen(Msg),&cli_addr);
						totalBytes+=strlen(Msg);
						NEnvio++;

					}else{

						FEnvia[pacoteEsperado % tam_janela].confirmado=1;
						qtdNaoConfirmado--;

					}

					pacoteEsperado++;
				}

			}
		}else{

			if (qtdNaoConfirmado != 0){

				//Enviando o Esperado
				printf("Enviado o esperado: %d\n\n", pacoteEsperado);
				Msg = pacoteToString(&FEnvia[pacoteEsperado % tam_janela].pacote);
				tp_sendto(sockfd,Msg,strlen(Msg),&cli_addr);
				NEnvio++;
				totalBytes+=strlen(Msg);

			}else if( bytes_lidos == 0){
				break;
			}

		}
	}while(1);

	//envia pacote de fechamento da janela
	empacota(&ACK,'f',pct_count, 0, "");
	ack = pacoteToString(&ACK);
	while( enviados ==0 ){
		enviados = tp_sendto(sockfd,ack,strlen(ack),&cli_addr);
	}

	//termina a contagem do tempo
	gettimeofday(&time2,NULL);

	//imprime o nome do arquivo enviado
	printf("Arquivo solicitado: %s\n", getConteudo(&recebido));

	//imprime total de bytes enviados
	printf("Total de Bytes enviados: %d\n", (int) totalBytes);

	//Imprime printf numero de pacotes enviados
	printf("Numero pacotes enviados: %d\n", NEnvio);

	//imprime o tempo total em microsegundos
	printf("tempo total up: : %f s\n" , (float) ( time2.tv_sec - time1.tv_sec + (time2.tv_usec - time1.tv_usec)/1000000.0) );
}
